package com.fosun.spr.core.proxy.aop;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;

import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import com.alibaba.fastjson.JSONObject;
import com.fosun.spr.core.base.model.CoBaseSpModel;
import com.fosun.spr.core.base.model.CoBaseSrModel;
import com.fosun.spr.core.cache.LocalCache;
import com.fosun.spr.core.constants.DataConstants;
import com.fosun.spr.core.constants.SprLog;
import com.fosun.spr.core.exception.ServiceException;
import com.fosun.spr.core.message.ResultCode;
import com.fosun.spr.core.proxy.annotation.SprControllerLog;
import com.fosun.spr.core.proxy.annotation.SprServiceLog;
import com.fosun.spr.core.utils.ExtAsserts;
 

@Aspect
@Component
public class SprLogAspect extends BaseAspect {

	@Pointcut("@annotation(com.fosun.spr.core.proxy.annotation.SprControllerLog)")
	public void controllerAspect() {
	}

	@Pointcut("@annotation(com.fosun.spr.core.proxy.annotation.SprServiceLog)")
	public void serviceAspect() {
	}

	private static String describe;// 方法描述
	private static String params;// 参数
	private static Object sprModel;
	private static Validator validator;

	

	/**
	 * 前置通知 controller
	 * 
	 * @param joinPoint
	 *            切点
	 */
	@Before("controllerAspect()")
	private void controBefore(JoinPoint joinPoint) {
		try {
			init();
			setParams(joinPoint, SprControllerLog.class);
			printLog(joinPoint, SprLog.STARTCONTRO.getName(), null);

		} catch (Exception e) {
			// 记录本地异常日志
			logger.error(SprLog.EXCECONTRO.getName());
			logger.error("异常信息:{}", e.getMessage());
			throw new ServiceException(e.getMessage());
		}

	}

	@AfterReturning(pointcut = "controllerAspect()", returning = "result")
	private void controAfter(JoinPoint joinPoint, Object result) {
		logger.info(SprLog.ENDCONTRO.getName());
		logger.info("==controller返回结果：{}",JSONObject.toJSONString(result));
		
		//commit by chensm@20170616
		// 获取controller方法返回值set到全局map{key/value}
//		synchronized (this) {
//			LocalCache.getInstance()
//			.set(joinPoint.getSignature().getName(), result);
//		}

	}

	@Before("serviceAspect()")
	private static synchronized void serviceBefore(JoinPoint joinPoint) {
		try {
			setParams(joinPoint, SprServiceLog.class);
			printLog(joinPoint, SprLog.STARTSERVICE.getName(), null);
			verifyServiceParam(joinPoint, DataConstants.SERVICE_BEFORE, null);
		} catch (Exception ex) {
			logger.error("service异常信息:{}", ex.getMessage());
			throw new ServiceException(ex.getMessage());
		}

	}

	@AfterReturning(pointcut = "serviceAspect()", returning = "result")
	private static synchronized void serviceAfter(JoinPoint joinPoint,
			Object result) {
		verifyServiceParam(joinPoint, DataConstants.SERVICE_AFTER, result);
		logger.info(SprLog.ENDSERVICE.getName());

	}

	@AfterThrowing(pointcut = "serviceAspect()", throwing = "e")
	private synchronized void serviceThrow(JoinPoint joinPoint, Throwable e) {

		try {
			setParams(joinPoint, SprServiceLog.class);
			printLog(joinPoint, SprLog.STARTEXCEPTION.getName(), e);

			logger.info(SprLog.ENDEXCEPTION.getName());
		} catch (RuntimeException ex) {
			logger.error("==异常通知异常==");
			logger.error("异常信息:{}", ex.getMessage());
			throw new ServiceException(ex.getMessage());
		}catch (Exception ex) {
			logger.error("==异常通知异常==");
			logger.error("异常信息:{}", ex.getMessage());
			throw new ServiceException(ex.getMessage());
		}
	}

	private static <T extends Annotation> String getMethodDesc(
			JoinPoint joinPoint, Class<T> claszz) throws Exception {
		String targetName = joinPoint.getTarget().getClass().getName();
		String methodName = joinPoint.getSignature().getName();
		Object[] arguments = joinPoint.getArgs();
		Class<?> targetClass = Class.forName(targetName);
		Method[] methods = targetClass.getMethods();
		String description = "";
		for (Method method : methods) {
			if (method.getName().equals(methodName)) {
				Class<?>[] clazzs = method.getParameterTypes();
				setSprModel(clazzs[DataConstants.NUM_0].newInstance());
				if (clazzs.length != arguments.length) {
					throw new ServiceException(ResultCode.PARAMTER_ERROR);
				}
				T t = method.getAnnotation(claszz);
				if (t instanceof SprControllerLog) {
					description = ((SprControllerLog) t).description();
				} else if (t instanceof SprServiceLog) {
					description = ((SprServiceLog) t).description();
				} else {
					throw new ServiceException(ResultCode.ANNOTATION_ERROR);
				}
				break;
			}
		}
		return description;
	}

	private static String getRequestParam(JoinPoint joinPoint) {
		Object[] getArgs=joinPoint.getArgs();
		return gsonTostring(getArgs[0]);
	}


	private static <T extends Annotation> void printLog(JoinPoint joinPoint,
			String log, Throwable e) {

		logger.info(log);
		if (e != null) {
			logger.info("异常代码:" + e.getClass().getName());
			logger.info("异常信息:" + e.getMessage());
			logger.info("异常方法:"
					+ (joinPoint.getTarget().getClass().getName() + "."
							+ joinPoint.getSignature().getName() + "()"));
			logger.info("方法描述:" + getDescribe());
			logger.info("请求参数:" + getParams());
		} else {
			logger.info("请求方法:"
					+ (joinPoint.getTarget().getClass().getName() + "."
							+ joinPoint.getSignature().getName() + "()"));
			logger.info("方法描述:" + getDescribe());
			logger.info("请求IP:" + getRemoteId());
			logger.info("请求参数:" + getParams());
		}
	}

	private static <T extends Annotation> void setParams(JoinPoint joinPoint,
			Class<T> classz) throws Exception {
		setDescribe(getMethodDesc(joinPoint, classz));
		setParams(getRequestParam(joinPoint));
	}

	private static void verifyServiceParam(JoinPoint joinPoint,
			String serviceStatus, Object result) {

		if (StringUtils.equals(serviceStatus, DataConstants.SERVICE_BEFORE)) {
			Object[] args = joinPoint.getArgs();
			Object baseSpModel=args[DataConstants.NUM_0];
			if (args.length != DataConstants.NUM_1) {
				throw new ServiceException(ResultCode.PARAMTER_WAY_ERROR);
			}
			if (!(baseSpModel instanceof CoBaseSpModel)) {
				throw new ServiceException(ResultCode.PARAMTER_ERROR);
			}
			ExtAsserts.notNull(baseSpModel);
			//verifySpModel();
		} else {

			if (!(result instanceof CoBaseSrModel)) {
				throw new ServiceException(ResultCode.PARAMTER_ERROR);
			}
			ExtAsserts.notNull(result);
		}

	}
	
	protected static void verifySpModel(){
		String reqs=StringUtils.substring(getParams(), DataConstants.NUM_1, StringUtils.lastIndexOf(getParams(), "]"));
		Object sprModelAll=JSONObject.parseObject(reqs, getSprModel().getClass());
		//verify spModel condition 
        Set<ConstraintViolation<Object>> validators = validator.validate(sprModelAll);
        for (ConstraintViolation<Object> constraintViolation : validators) {
        	if(constraintViolation!=null){
        		throw new ServiceException(constraintViolation.getPropertyPath() + ":" + constraintViolation.getMessage());
        	}
        }
	}
	
	private static void init(){
		if(getValidator()==null){
			setValidator(Validation.buildDefaultValidatorFactory().getValidator());
		}
	}
	
	public static String getDescribe() {
		return describe;
	}

	public static void setDescribe(String describe) {
		SprLogAspect.describe = describe;
	}

	public static String getParams() {
		return params;
	}

	public static void setParams(String params) {
		SprLogAspect.params = params;
	}

	public static Object getSprModel() {
		return sprModel;
	}

	public static void setSprModel(Object sprModel) {
		SprLogAspect.sprModel = sprModel;
	}

	public static Validator getValidator() {
		return validator;
	}

	public static void setValidator(Validator validator) {
		SprLogAspect.validator = validator;
	}

}
